// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "xdc-server.h"

#include <errno.h>
#include <fcntl.h>
#include <lib/fit/defer.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <sys/file.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>

#include <cassert>

#include <xdc-host-utils/conn.h>
#include <xdc-server-utils/msg.h>
#include <xdc-server-utils/packet.h>
#include <xdc-server-utils/stream.h>

#include "usb-handler.h"

namespace xdc {

static constexpr uint32_t MAX_PENDING_CONN_BACKLOG = 128;
static const char* XDC_LOCK_PATH = "/tmp/xdc.lock";

void Client::SetStreamId(uint32_t stream_id) {
  registered_ = true;
  stream_id_ = stream_id;
}

void Client::SetConnected(bool connected) {
  if (connected == connected_) {
    fprintf(stderr, "tried to set client with stream id %u as %s again.\n", stream_id(),
            connected ? "connected" : "disconnected");
    return;
  }
  printf("client with stream id %u is now %s to the xdc device stream.\n", stream_id(),
         connected ? "connected" : "disconnected");
  connected_ = connected;
}

bool Client::UpdatePollState(bool usb_writable) {
  short updated_events = events_;
  // We want to poll for the client readable signal if:
  //  - The client has not yet registered a stream id, or,
  //  - The xdc stream of the client's id is ready to be written to.
  if (!stream_id() || (usb_writable && connected())) {
    updated_events |= POLLIN;
  } else {
    updated_events &= ~(POLLIN);
  }
  // We need to poll for the client writable signal if we have data to send to the client.
  if (completed_reads_.size() > 0) {
    updated_events |= POLLOUT;
  } else {
    updated_events &= ~POLLOUT;
  }
  if (updated_events != events_) {
    events_ = updated_events;
    return true;
  }
  return false;
}

void Client::AddCompletedRead(std::unique_ptr<UsbHandler::Transfer> transfer) {
  completed_reads_.push_back(std::move(transfer));
}

void Client::ProcessCompletedReads(const std::unique_ptr<UsbHandler>& usb_handler) {
  for (auto iter = completed_reads_.begin(); iter != completed_reads_.end();) {
    std::unique_ptr<UsbHandler::Transfer>& transfer = *iter;

    unsigned char* data = transfer->data() + transfer->offset();
    ssize_t len_to_write = transfer->actual_length() - transfer->offset();

    ssize_t total_written = 0;
    while (total_written < len_to_write) {
      ssize_t res = send(fd(), data + total_written, len_to_write - total_written, 0);
      if (res < 0) {
        if (errno == EAGAIN) {
          fprintf(stderr, "can't send completed read to client currently\n");
          // Need to wait for client to be writable again.
          return;
        } else {
          fprintf(stderr, "can't write to client, err: %s\n", strerror(errno));
          return;
        }
      }
      total_written += res;
      ssize_t offset = transfer->offset() + res;
      if (offset < std::numeric_limits<int>::min() || offset > std::numeric_limits<int>::max()) {
        fprintf(stderr, "offset out of range\n");
        exit(-1);
      }
      __UNUSED bool set_offset_result = transfer->SetOffset(static_cast<int>(offset));
      assert(set_offset_result);
    }
    usb_handler->RequeueRead(std::move(transfer));
    iter = completed_reads_.erase(iter);
  }
}

zx_status_t Client::ProcessWrites(const std::unique_ptr<UsbHandler>& usb_handler) {
  if (!connected()) {
    return ZX_ERR_SHOULD_WAIT;
  }
  while (true) {
    if (!pending_write_) {
      pending_write_ = usb_handler->GetWriteTransfer();
      if (!pending_write_) {
        return ZX_ERR_SHOULD_WAIT;  // No transfers currently available.
      }
    }
    // If there is no pending data to transfer, read more from the client.
    if (!has_write_data()) {
      // Read from the client into the usb transfer buffer. Leave space for the header.
      unsigned char* buf = pending_write_->write_data_buffer();

      ssize_t n = recv(fd(), buf, UsbHandler::Transfer::MAX_WRITE_DATA_SIZE, 0);
      if (n == 0) {
        return ZX_ERR_PEER_CLOSED;
      } else if (n == EAGAIN) {
        return ZX_ERR_SHOULD_WAIT;
      } else if (n < 0) {
        fprintf(stderr, "recv got unhandled err: %s\n", strerror(errno));
        return ZX_ERR_IO;
      }
      pending_write_->FillHeader(stream_id(), n);
    }
    if (usb_handler->writable()) {
      pending_write_ = usb_handler->QueueWriteTransfer(std::move(pending_write_));
      if (pending_write_) {
        // Usb handler was busy and returned the write.
        return ZX_ERR_SHOULD_WAIT;
      }
    } else {
      break;  // Usb handler is busy, need to wait for some writes to complete.
    }
  }
  return ZX_ERR_SHOULD_WAIT;
}

void Client::ReturnTransfers(const std::unique_ptr<UsbHandler>& usb_handler) {
  for (auto& transfer : completed_reads_) {
    usb_handler->RequeueRead(std::move(transfer));
  }
  completed_reads_.clear();

  if (pending_write_) {
    usb_handler->ReturnWriteTransfer(std::move(pending_write_));
  }
}

// static
std::unique_ptr<XdcServer> XdcServer::Create() {
  auto conn = std::make_unique<XdcServer>(ConstructorTag{});
  if (!conn->Init()) {
    return nullptr;
  }
  return conn;
}

bool XdcServer::Init() {
  usb_handler_ = UsbHandler::Create();
  if (!usb_handler_) {
    return false;
  }

  socket_fd_.reset(socket(AF_UNIX, SOCK_STREAM, 0));
  if (!socket_fd_) {
    fprintf(stderr, "failed to create socket, err: %s\n", strerror(errno));
    return false;
  }
  sockaddr_un addr = {};
  addr.sun_family = AF_UNIX;
  strncpy(addr.sun_path, XDC_SOCKET_PATH, sizeof(addr.sun_path));

  // Check if another instance of the xdc server is running.
  socket_lock_fd_.reset(open(XDC_LOCK_PATH, O_CREAT | O_RDONLY, 0666));
  if (!socket_lock_fd_) {
    return false;
  }
  int res = flock(socket_lock_fd_.get(), LOCK_EX | LOCK_NB);
  if (res != 0) {
    fprintf(stderr, "Failed to acquire socket lock, err: %s.\n", strerror(errno));
    return false;
  }
  // Remove the socket file if it exists.
  unlink(XDC_SOCKET_PATH);
  if (bind(socket_fd_.get(), (sockaddr*)&addr, sizeof(addr)) != 0) {
    fprintf(stderr, "Could not bind socket with pathname: %s, err: %s\n", XDC_SOCKET_PATH,
            strerror(errno));
    return false;
  }
  if (listen(socket_fd_.get(), MAX_PENDING_CONN_BACKLOG) < 0) {
    fprintf(stderr, "Could not listen on socket fd: %d, err: %s\n", socket_fd_.get(),
            strerror(errno));
    return false;
  }
  return true;
}

void XdcServer::UpdateClientPollEvents() {
  for (auto iter : clients_) {
    std::shared_ptr<Client> client = iter.second;
    bool changed = client->UpdatePollState(usb_handler_->writable());
    if (changed) {
      // We need to update the corresponding file descriptor in the poll_fds_ array
      // passed to poll.
      int fd = client->fd();
      auto is_fd = [fd](auto& elem) { return elem.fd == fd; };
      auto fd_iter = std::find_if(poll_fds_.begin(), poll_fds_.end(), is_fd);
      if (fd_iter == poll_fds_.end()) {
        fprintf(stderr, "could not find pollfd for client with fd %d\n", fd);
        continue;
      }
      fd_iter->events = client->events();
    }
  }
}

void XdcServer::UpdateUsbHandlerFds() {
  std::map<int, short> added_fds;
  std::set<int> removed_fds;
  usb_handler_->GetFdUpdates(added_fds, removed_fds);

  for (auto iter : added_fds) {
    int fd = iter.first;
    short events = iter.second;

    auto match = std::find_if(poll_fds_.begin(), poll_fds_.end(),
                              [&fd](auto& pollfd) { return pollfd.fd == fd; });
    if (match != poll_fds_.end()) {
      fprintf(stderr, "already have usb handler fd: %d\n", fd);
      continue;
    }
    poll_fds_.push_back(pollfd{fd, events, 0});
    printf("usb handler added fd: %d\n", fd);
  }
  for (auto fd : removed_fds) {
    auto match = std::remove_if(poll_fds_.begin(), poll_fds_.end(),
                                [&fd](auto& pollfd) { return pollfd.fd == fd; });
    if (match == poll_fds_.end()) {
      fprintf(stderr, "could not find usb handler fd: %d to delete\n", fd);
      continue;
    }
    poll_fds_.erase(match, poll_fds_.end());
    printf("usb handler removed fd: %d\n", fd);
  }
}

void XdcServer::Run() {
  signal(SIGPIPE, SIG_IGN);  // Prevent clients from causing SIGPIPE.

  printf("Waiting for connections on: %s\n", XDC_SOCKET_PATH);

  // Listen for new client connections.
  poll_fds_.push_back(pollfd{socket_fd_.get(), POLLIN, 0});

  // Initialize to true as we want to get the initial usb handler fds.
  bool update_usb_handler_fds = true;

  for (;;) {
    if (update_usb_handler_fds) {
      UpdateUsbHandlerFds();
      update_usb_handler_fds = false;
    }

    // poll expects an array of pollfds.
    int num = poll(&poll_fds_[0], static_cast<nfds_t>(poll_fds_.size()), -1 /* timeout */);
    if (num < 0) {
      fprintf(stderr, "poll failed, err: %s\n", strerror(errno));
      break;
    }
    // Not using an iterator for poll_fds_ as we might add/remove elements.
    size_t num_sockets = poll_fds_.size();
    size_t i = 0;
    while (i < num_sockets) {
      if (poll_fds_[i].fd == socket_fd_.get()) {
        // A new client is trying to connect.
        if (poll_fds_[i].revents & POLLIN) {
          ClientConnect();
          // Don't need to increment num_sockets as there aren't poll events for it yet.
        }
      } else if (usb_handler_->IsValidFd(poll_fds_[i].fd)) {
        if (poll_fds_[i].revents) {
          std::vector<std::unique_ptr<UsbHandler::Transfer>> completed_reads;
          update_usb_handler_fds = usb_handler_->HandleEvents(completed_reads);

          SendQueuedCtrlMsgs();

          for (auto& usb_transfer : completed_reads) {
            UsbReadComplete(std::move(usb_transfer));
          }
        }
      } else {
        auto iter = clients_.find(poll_fds_[i].fd);
        if (iter == clients_.end()) {
          fprintf(stderr, "poll returned an unknown fd: %d\n", poll_fds_[i].fd);
          poll_fds_.erase(poll_fds_.begin() + i);
          --num_sockets;
          continue;
        }

        std::shared_ptr<Client> client = iter->second;

        // Received client disconnect signal.
        // Only remove the client if the corresponding xdc device stream is offline.
        // Otherwise the client may still have data buffered to send to the usb handler,
        // and we will wait until reading from the client returns zero (disconnect).
        bool delete_client = (poll_fds_[i].revents & POLLHUP) && !client->connected();

        // Check if the client had pending data to write, or signalled new data available.
        bool do_write = client->has_write_data() && usb_handler_->writable() && client->connected();
        bool new_data_available = poll_fds_[i].revents & POLLIN;
        if (!delete_client && (do_write || new_data_available)) {
          if (!client->registered()) {
            // Delete the client if registering the stream failed.
            delete_client = !RegisterStream(client);
          }
          if (!delete_client) {
            zx_status_t status = client->ProcessWrites(usb_handler_);
            if (status == ZX_ERR_PEER_CLOSED) {
              delete_client = true;
            }
          }
        }

        if (delete_client) {
          client->ReturnTransfers(usb_handler_);
          // Notify the host server that the stream is now offline.
          if (client->stream_id()) {
            NotifyStreamState(client->stream_id(), false /* online */);
          }
          poll_fds_.erase(poll_fds_.begin() + i);
          --num_sockets;
          printf("fd %d stream %u disconnected\n", client->fd(), client->stream_id());
          clients_.erase(iter);
          continue;
        }
        client->ProcessCompletedReads(usb_handler_);
      }
      ++i;
    }
    UpdateClientPollEvents();
  }
}

void XdcServer::ClientConnect() {
  struct sockaddr_un addr;
  socklen_t len = sizeof(addr);
  // Most of the time we want non-blocking transfers, so we can handle other clients / libusb.
  int client_fd = accept(socket_fd_.get(), (struct sockaddr*)&addr, &len);
  if (client_fd < 0) {
    fprintf(stderr, "Socket accept failed, err: %s\n", strerror(errno));
    return;
  }
  if (clients_.count(client_fd) > 0) {
    fprintf(stderr, "Client already connected, socket fd: %d\n", client_fd);
    return;
  }
  int flags = fcntl(client_fd, F_GETFL, 0);
  if (flags < 0) {
    fprintf(stderr, "Could not get socket flags, err: %s\n", strerror(errno));
    close(client_fd);
    return;
  }
  int res = fcntl(client_fd, F_SETFL, flags | O_NONBLOCK);
  if (res != 0) {
    fprintf(stderr, "Could not set socket as nonblocking, err: %s\n", strerror(errno));
    close(client_fd);
    return;
  }
  printf("Client connected, socket fd: %d\n", client_fd);
  clients_[client_fd] = std::make_shared<Client>(client_fd);
  poll_fds_.push_back(pollfd{client_fd, POLLIN, 0});
}

bool XdcServer::RegisterStream(std::shared_ptr<Client> client) {
  RegisterStreamRequest stream_id;
  ssize_t n = recv(client->fd(), &stream_id, sizeof(stream_id), MSG_WAITALL);
  if (n != sizeof(stream_id)) {
    fprintf(stderr, "failed to read stream id from client fd: %d, got len: %ld, got err: %s\n",
            client->fd(), n, strerror(errno));
    return false;
  }
  // Client has disconnected. This will be handled in the main poll thread.
  if (n == 0) {
    return false;
  }
  RegisterStreamResponse resp = false;
  if (stream_id == DEBUG_STREAM_ID_RESERVED) {
    fprintf(stderr, "cannot register stream id %u\n", DEBUG_STREAM_ID_RESERVED);
  } else if (GetClient(stream_id)) {
    fprintf(stderr, "stream id %u was already registered\n", stream_id);
  } else {
    client->SetStreamId(stream_id);
    printf("registered stream id %u\n", stream_id);
    NotifyStreamState(stream_id, true /* online */);
    if (dev_stream_ids_.count(stream_id)) {
      client->SetConnected(true);
    }
    resp = true;
  }

  ssize_t res = send(client->fd(), &resp, sizeof(resp), MSG_WAITALL);
  if (res != sizeof(resp)) {
    // Failed to send reply, disconnect the client.
    return false;
  }
  return resp;
}

std::shared_ptr<Client> XdcServer::GetClient(uint32_t stream_id) {
  auto is_client = [stream_id](auto& pair) -> bool {
    return pair.second->stream_id() == stream_id;
  };
  auto iter = std::find_if(clients_.begin(), clients_.end(), is_client);
  return iter == clients_.end() ? nullptr : iter->second;
}

void XdcServer::UsbReadComplete(std::unique_ptr<UsbHandler::Transfer> transfer) {
  auto requeue = fit::defer([&]() { usb_handler_->RequeueRead(std::move(transfer)); });

  bool is_new_packet;
  uint32_t stream_id;

  zx_status_t status = xdc_update_packet_state(&read_packet_state_, transfer->data(),
                                               transfer->actual_length(), &is_new_packet);
  if (status != ZX_OK) {
    fprintf(stderr, "error processing transfer: %d, dropping read of size %d\n", status,
            transfer->actual_length());
  }
  stream_id = read_packet_state_.header.stream_id;
  if (is_new_packet && stream_id == XDC_MSG_STREAM) {
    HandleCtrlMsg(transfer->data(), transfer->actual_length());
    return;
  }
  // Pass the completed transfer to the registered client, if any.
  auto client = GetClient(stream_id);
  if (!client) {
    fprintf(stderr, "No client registered for stream %u, dropping read of size %d\n", stream_id,
            transfer->actual_length());
    return;
  }
  // If it is the start of a new packet, the client should begin reading after the header.
  int offset = is_new_packet ? sizeof(xdc_packet_header_t) : 0;
  __UNUSED bool set_offset_result = transfer->SetOffset(offset);
  assert(set_offset_result);
  client->AddCompletedRead(std::move(transfer));
  requeue.cancel();
}

void XdcServer::HandleCtrlMsg(unsigned char* transfer_buf, int transfer_len) {
  int data_len = transfer_len - (int)sizeof(xdc_packet_header_t);
  if (data_len < (int)sizeof(xdc_msg_t)) {
    fprintf(stderr, "malformed msg, got %d bytes, need %lu\n", data_len, sizeof(xdc_msg_t));
    return;
  }
  xdc_msg_t* msg = reinterpret_cast<xdc_msg_t*>(transfer_buf + sizeof(xdc_packet_header_t));
  switch (msg->opcode) {
    case XDC_NOTIFY_STREAM_STATE: {
      uint32_t stream_id = msg->notify_stream_state.stream_id;
      bool online = msg->notify_stream_state.online;

      auto dev_stream = dev_stream_ids_.find(stream_id);
      bool saved_online_state = dev_stream != dev_stream_ids_.end();
      if (online == saved_online_state) {
        fprintf(stderr, "tried to set stream %u to %s again\n", stream_id,
                online ? "online" : "offline");
        return;
      }
      if (online) {
        dev_stream_ids_.insert(stream_id);
      } else {
        dev_stream_ids_.erase(dev_stream);
      }
      printf("xdc device stream id %u is now %s\n", stream_id, online ? "online" : "offline");

      // Update the host client's connected status.
      auto client = GetClient(stream_id);
      if (!client) {
        break;
      }
      client->SetConnected(online);
      break;
    }
    default:
      fprintf(stderr, "unknown msg opcode: %u\n", msg->opcode);
  }
}

void XdcServer::NotifyStreamState(uint32_t stream_id, bool online) {
  xdc_msg_t msg = {.opcode = XDC_NOTIFY_STREAM_STATE,
                   .notify_stream_state.stream_id = stream_id,
                   .notify_stream_state.online = online};
  queued_ctrl_msgs_.push_back(msg);
  SendQueuedCtrlMsgs();
}

bool XdcServer::SendCtrlMsg(xdc_msg_t& msg) {
  std::unique_ptr<UsbHandler::Transfer> transfer = usb_handler_->GetWriteTransfer();
  if (!transfer) {
    return false;
  }
  __UNUSED zx_status_t res = transfer->FillData(
      DEBUG_STREAM_ID_RESERVED, reinterpret_cast<unsigned char*>(&msg), sizeof(msg));
  assert(res == ZX_OK);  // Should not fail.
  transfer = usb_handler_->QueueWriteTransfer(std::move(transfer));
  bool queued = !transfer;
  if (!queued) {
    usb_handler_->ReturnWriteTransfer(std::move(transfer));
  }
  return queued;
}

void XdcServer::SendQueuedCtrlMsgs() {
  auto msgs_iter = queued_ctrl_msgs_.begin();
  while (msgs_iter != queued_ctrl_msgs_.end()) {
    bool sent = SendCtrlMsg(*msgs_iter);
    if (sent) {
      msgs_iter = queued_ctrl_msgs_.erase(msgs_iter);
    } else {
      // Need to wait.
      return;
    }
  }
}

}  // namespace xdc

int main(int argc, char** argv) {
  printf("Starting XHCI Debug Capability server...\n");
  std::unique_ptr<xdc::XdcServer> xdc_server = xdc::XdcServer::Create();
  if (!xdc_server) {
    return -1;
  }
  xdc_server->Run();
  return 0;
}
